import re
from datetime import datetime, timedelta

from django import newforms as forms
from django.contrib.sites.models import Site
from django.contrib.auth.decorators import login_required
from django.conf import settings
from django.core.mail import send_mail
from django.core.paginator import ObjectPaginator, InvalidPage
from django.shortcuts import get_object_or_404, render_to_response
from django.http import Http404, HttpResponseRedirect
from django.template import RequestContext, loader

from nyxsis.shortcuts import PermissionDenied

from models import Forum, Thread, Post
from forms import PostBodyForm

def _email_notification(request, post):
    # Email Notification
    current_domain = Site.objects.get_current().domain
    subject = 'New post in "%s" at %s' % (post.thread.title, current_domain)
    message_template = loader.get_template('forum/watching_email.txt')
    global_context = RequestContext(request, {
        'site_url': 'http://%s' % current_domain,
        'thread': post.thread,
        'post': post,
    })
    for user in post.thread.watching.all():
        if user.id == request.user.id:
            continue # Don't E-mail yourself
        if post.thread.watching_emailed.filter(pk=user.id).count() > 0:
            continue # Don't E-mail if user hasn't read the last E-mail
        user_context = global_context
        user_context['user'] = user
        message = message_template.render(user_context)
        send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [user.email])
        # User has been E-mailed, don't E-mail them again
        post.thread.watching_emailed.add(user)

def forum(request, forum):
    """
    Displays a list of threads within a forum. Threads are sorted by their
    sticky flag, followed by their most recent post.
    """
    
    forum   = get_object_or_404(Forum, slug=forum)
    threads = forum.thread_set.select_related(depth=1)
    total_threads = len(threads)
    
    # Paginate
    page = 1
    if 'page' in request.GET:
        page = int(request.GET['page'])
    paginator = ObjectPaginator(threads, 20)
    try:
        threads = paginator.get_page(page - 1)
    except InvalidPage:
        raise Http404
    
    return render_to_response(
        'forum/forum.html',
        RequestContext(request, {
            'forum': forum,
            'thread_list': threads,
            'total_threads': total_threads,
            'is_paginated': paginator.pages > 1, 
            'page': page,
            'pages': paginator.pages,
        })
    )

@login_required
def watched(request):
    """
    Displays a list of threads that the current user is watching
    """
    start_time = datetime.now() - timedelta(1)
    threads    = request.user.thread_set.order_by('-thread_latest_post').select_related(depth=1)
    total_threads = len(threads)
    
    # Paginate
    page = 1
    if 'page' in request.GET:
        page = int(request.GET['page'])
    paginator = ObjectPaginator(threads, 20)
    try:
        threads = paginator.get_page(page - 1)
    except InvalidPage:
        raise Http404
    
    return render_to_response(
        'forum/watched.html',
        RequestContext(request, {
            'thread_list': threads,
            'total_threads': total_threads,
            'is_paginated': paginator.pages > 1,
            'page': page,
            'pages': paginator.pages,
        })
    )

def thread(request, forum, thread):
    """
    Increments the viewed count on a thread then displays the posts for that
    thread, in chronological order.
    """
    
    thread  = get_object_or_404(Thread, pk=thread)
    #posts   = thread.post_set.select_related(depth=1) # Bug: Causes User to become a Thread?
    posts   = thread.post_set.all()
    starter = posts[0]
    
    thread.views += 1
    thread.save()
    
    # If user has watched thread make them able to be notified again
    if request.user.id:
        thread.watching_emailed.remove(request.user)
    
    # Paginate
    page = 1
    if 'page' in request.GET:
        page = int(request.GET['page'])
    paginator = ObjectPaginator(posts, 20)
    try:
        posts = paginator.get_page(page - 1)
    except InvalidPage:
        raise Http404
    
    return render_to_response(
        'forum/thread.html',
        RequestContext(request, {
            'forum': thread.forum,
            'thread': thread,
            'thread_watching': thread.is_watching(request.user),
            'posts': posts,
            'starter': starter,
            'is_paginated': paginator.pages > 1, 
            'page': page,
            'pages': paginator.pages,
            'has_next': paginator.has_next_page(page - 1), 
            'has_previous': paginator.has_previous_page(page - 1), 
        })
    )

@login_required
def thread_new(request, forum):
    """
    Creates a new thread.
    Only allows a user to post if they're logged in.
    """
    
    forum = get_object_or_404(Forum, slug=forum)
    
    thread_fields = ('title',)
    if request.user.get_profile().is_moderator:
        thread_fields = ('title', 'sticky', 'closed',)
    form_thread = forms.form_for_model(Thread, fields=thread_fields)
    form_post   = forms.form_for_model(Post, form=PostBodyForm, fields=('body',))
    
    context = {'forum': forum}
    
    if request.method == 'POST':
        form_thread = form_thread(request.POST)
        form_post = form_post(request.POST)
        if form_thread.is_valid() and form_post.is_valid():
            if 'preview' in request.POST:
                context['preview'] = form_post.cleaned_data['body']
            else:
                thread = Thread(
                    forum=forum,
                    title=request.POST.get('title'),
                )
                thread.save()
                post = Post(
                    thread=thread,
                    author=request.user,
                    body=request.POST.get('body'),
                )
                post.save()
                return HttpResponseRedirect(thread.get_absolute_url())
    else:
        form_thread = form_thread()
        form_post = form_post()
    
    context['form_thread'] = form_thread
    context['form_post']   = form_post
    
    return render_to_response(
        'forum/thread_new.html',
        RequestContext(request, context)
    )

@login_required
def thread_watch(request, forum, thread):
    """
    Watch a thread
    """
    thread = get_object_or_404(Thread, pk=thread)
    if not thread.is_watching(request.user):
        thread.watching.add(request.user)
        request.user.message_set.create(message='Thread has been watched.')
    return HttpResponseRedirect(thread.get_absolute_url())

@login_required
def thread_unwatch(request, forum, thread):
    """
    Stop watching a thread
    """
    thread = get_object_or_404(Thread, pk=thread)
    if thread.is_watching(request.user):
        thread.watching.remove(request.user)
        request.user.message_set.create(message='Thread has been unwatched.')
    return HttpResponseRedirect(thread.get_absolute_url())

@login_required
def post_edit(request, forum, thread, post):
    """
    Allows a user to edit their post.
    """
    
    post      = get_object_or_404(Post, pk=post)
    orig_body = post.body
    
    # Don't allow non-author to edit
    if post.author.id != request.user.id and not request.user.get_profile().is_moderator:
        return PermissionDenied(request, 'You do not have permission to edit this post.')
    
    context = {
        'forum':  post.thread.forum,
        'thread': post.thread,
        'post':   post,
    }
    
    if post.thread.get_first_post().id == post.id:
        # Edit the thread as well
        thread_fields = ('title',)
        if request.user.get_profile().is_moderator:
            thread_fields = ('title', 'sticky', 'closed',)
        instanceform_thread = forms.form_for_instance(post.thread, fields=thread_fields)
        instanceform_post   = forms.form_for_instance(post, form=PostBodyForm, fields=('body',))
        
        if request.method == 'POST':
            form_thread = instanceform_thread(request.POST)
            form_post   = instanceform_post(request.POST)
            if form_thread.is_valid() and form_post.is_valid():
                if 'preview' in request.POST:
                    context['preview'] = form_post.cleaned_data['body']
                else:
                    form_thread.save()
                    form_post.save()
                    request.user.message_set.create(message='Your post has been edited.')
                    
                    # Notify watchers if it's been changed quite a bit and is the last post
                    if post == post.thread.thread_latest_post \
                    and abs(len(post.body) - len(orig_body)) > 100:
                        _email_notification(request, post)
                    
                    return HttpResponseRedirect(post.get_absolute_url())
        else:
            form_thread = instanceform_thread()
            form_post   = instanceform_post(post.__dict__)
        
        context['form_thread'] = form_thread
        context['form_post']   = form_post
        
        return render_to_response('forum/thread_edit.html',
            RequestContext(request, context))
    else:
        # Just edit the post
        instanceform = forms.form_for_instance(post, form=PostBodyForm, fields=('body',))
        
        if request.method == 'POST':
            form = instanceform(request.POST)
            if form.is_valid():
                if 'preview' in request.POST:
                    context['preview'] = form.cleaned_data['body']
                else:
                    form.save()
                    request.user.message_set.create(message='Your post has been edited.')
                    
                    # Notify watchers if it's been changed quite a bit and is the last post
                    if post == post.thread.thread_latest_post \
                    and abs(len(post.body) - len(orig_body)) > 100:
                        _email_notification(request, post)
                    
                    return HttpResponseRedirect(post.get_absolute_url())
        else:
            form = instanceform(post.__dict__)
        
        context['form'] = form
        
        return render_to_response(
            'forum/post_edit.html',
            RequestContext(request, context)
        )

@login_required
def post_quote(request, forum, thread, post):
    post  = get_object_or_404(Post, pk=post)
    quote = '[quote=%s]%s[/quote]\n\n' % (post.author, post.body)
    
    return post_new(request, forum, thread, quote)

@login_required
def post_new(request, forum, thread, preload=None):
    """
    If a thread isn't closed, and the user is logged in, post a reply to a
    thread. Note we don't have "nested" replies at this stage.
    """
    
    thread = get_object_or_404(Thread, pk=thread)
    
    # Topic must not be closed
    if thread.closed and not request.user.get_profile().is_moderator:
        return PermissionDenied(request, 'You cannot post in a closed thread.')
    
    form = forms.form_for_model(Post, form=PostBodyForm, fields=('body',))
    
    context = {
        'forum':  thread.forum,
        'thread': thread,
    }
    
    if request.method == 'POST':
        form = form(request.POST)
        if form.is_valid():
            if 'preview' in request.POST:
                context['preview'] = form.cleaned_data['body']
            else:
                # Anti-double-post
                last_post = thread.thread_latest_post
                if request.user == last_post.author and datetime.now() - last_post.time < timedelta(2):
                    # Merge with the last post if not older than two days
                    said_edit = False
                    result = re.compile(r'\W*(\w+)').match(form.cleaned_data['body'])
                    if result:
                        # Don't add "Update" if it's already there
                        word = result.group(1).lower()
                        if word == 'update' or word == 'edit':
                            said_edit = True
                    if said_edit:
                        last_post.body = '%s\n\n%s' % (last_post.body, form.cleaned_data['body'])
                    else:
                        last_post.body = '%s\n\n*Update:*\n\n%s' % (last_post.body, form.cleaned_data['body'])
                    last_post.save()
                    post = last_post
                else:
                    post = Post(
                        thread=thread, 
                        author=request.user,
                        body=form.cleaned_data['body'],
                    )
                    post.save()
                
                # Email Notification
                _email_notification(request, post)
                
                return HttpResponseRedirect(post.get_absolute_url())
    else:
        if preload:
            form = form(initial={'body':preload})
        else:
            form = form()
    
    context['form'] = form
    
    return render_to_response(
        'forum/post_new.html',
        RequestContext(request, context)
    )

@login_required
def post_delete(request, forum, thread, post):
    """
    Allows a user to delete their post.
    """
    
    post = get_object_or_404(Post, pk=post)
    
    context = {
        'forum':  post.thread.forum,
        'thread': post.thread,
        'post':   post,
    }
    
    # Don't allow non-author to delete
    if post.author.id != request.user.id and not request.user.get_profile().is_moderator:
        return PermissionDenied(request, 'You do not have permission to delete this post.')
    
    if post.thread.get_first_post().id == post.id:
        # Delete the thread as well
        context['is_thread'] = True
        if request.method == 'POST' and 'delete' in request.POST:
            message  = 'The thread titled "%s" has been deleted.' % post.thread.title
            redirect = post.thread.forum.get_absolute_url()
            post.thread.delete()
            request.user.message_set.create(message=message)
            return HttpResponseRedirect(redirect)
    else: # Just the post
        context['is_thread'] = False
        if request.method == 'POST' and 'delete' in request.POST:
            redirect = post.thread.get_absolute_url()
            post.delete()
            request.user.message_set.create(message='Your post has been deleted.')
            return HttpResponseRedirect(redirect)
    
    return render_to_response(
        'forum/post_delete.html',
        RequestContext(request, context)
    )
